Mini-Project #03: Visualizing and Maintaining the Green Canopy of NYC
Author
Nusrat Akter
Published
November 16, 2025
What Is The Mission?
New York City’s parks and trees play a huge role in making the city a healthier and more livable place. With nearly 900,000 trees across the five boroughs, NYC’s urban forest provides cleaner air, shade in the summer, and a connection to nature that many people rely on. The NYC Parks Department invests a significant budget and workforce to care for more than 30,000 acres of public parkland, showing how important these green spaces are to the city. In this mini-project, I will explore data from the NYC TreeMap to create visualizations that help us better understand the trees around us. Based on what I find, I will propose a new program that aims to make the benefits of NYC’s trees more accessible to all communities, especially those that may not have as much green space today.
Data Acquisition
This code loads the NYC Council District shapefile and converts it into the WGS84 coordinate system so it can be used for mapping. It then reads in previously cleaned NYC tree point data, including a full dataset and a smaller 50,000-tree sample. These datasets will be used to create visualizations and maps of NYC’s tree distribution.
Show The Code
library(sf)library(dplyr)library(here)library(httr2)library(glue)library(ggplot2)library(leaflet)# read + transform NYC council district shapefilenycc_sf <-st_read(here("data/mp03/nycc_25c/nycc.shp"), quiet =TRUE)nycc_wgs84 <-st_transform(nycc_sf, crs ="WGS84")# pre-saved cleaned tree datatrees_pts <-readRDS("data/mp03/trees_points.rds")trees_sample <-readRDS("data/mp03/trees_sample50k.rds")
Note
By running this code beforehand, we avoided errors, slow rendering speeds, and max usage of RAM storage.
Task 1: Download NYC City Council District Boundaries
This function is used to load the NYC City Council District boundaries so we can map them later. I already created the folder, downloaded the zip file, and unzipped it, so now the function just reads the shapefile and transforms it to WGS84 so it matches the coordinate system used in the rest of the project. After that, it returns the cleaned district boundary data so we can use it in our maps and visualizations.
Task 2: Download NYC Forestry Tree Points
Show The Code
library(httr2)library(sf)library(dplyr)library(glue)library(here)# responsibly download NYC street trees from the socrata APIget_tree_points <-function(limit =50000) { base_url <-"https://data.cityofnewyork.us/resource/uvpi-gqnh.geojson"dir.create(here("data/mp03"), showWarnings =FALSE) page <-0 files <-c()repeat { file_path <-here(glue("data/mp03/trees_page_{sprintf('%03d', page)}.geojson"))# download only if not already savedif (!file.exists(file_path)) { req <-request(base_url) |>req_url_query(`$limit`= limit, `$offset`= page * limit) |>req_perform()writeBin(resp_body_raw(req), file_path) } files <-c(files, file_path)# stop when the file is very small (last page)if (file.info(file_path)$size <2000) break page <- page +1 }# read all downloaded pages and combine tree_list <-lapply(files, st_read, quiet =TRUE) trees <-bind_rows(tree_list)return(trees)}# load from RDS if available, otherwise build tree dataset and save itif (file.exists(here("data/mp03/trees_points.rds"))) { trees_pts <-readRDS(here("data/mp03/trees_points.rds"))} else { trees_pts <-get_tree_points()saveRDS(trees_pts, here("data/mp03/trees_points.rds"))}# optional sample for faster mapping if it already existsif (file.exists(here("data/mp03/trees_sample50k.rds"))) { trees_sample <-readRDS(here("data/mp03/trees_sample50k.rds"))}
Note
I used the HTTR2 package to download the NYC Street Tree Census data directly from the Socrata API. The data was pulled in pages using limit and offset, and each page was only downloaded if it wasn’t already saved. After combining all pages into one dataset, I saved it (trees_points.rds) so future renders would be much faster.
Show The Code
library(sf)library(dplyr)# load pre-cleaned tree pointstrees_pts <-readRDS("data/mp03/trees_points.rds")
Note
This code loads my saved NYC tree dataset(trees_points.rds) so I can use it for mapping and analysis without downloading it again.
Task 3: Interactive Tree Data Map
Show The Code
#| echo: true#| message: false#| warning: false#| codefold: truelibrary(leaflet)library(dplyr)# ensure trees_sample exists; if not, create a sample if (!exists("trees_sample")) {if (file.exists("data/mp03/trees_sample50k.rds")) { trees_sample <-readRDS("data/mp03/trees_sample50k.rds") } else {set.seed(123) trees_sample <- trees_pts |>slice_sample(n =50000)saveRDS(trees_sample, "data/mp03/trees_sample50k.rds") }}# how many trees are being mapped?tree_count <-nrow(trees_sample)leaflet(options =leafletOptions(minZoom =9, maxZoom =18)) |>addProviderTiles("CartoDB.Positron") |>addPolygons(data = nycc_wgs84,fillColor ="rgba(173,216,230,0.2)",color ="#5C7EA8",weight =1,fillOpacity =0.2,label =~paste("Council District", CounDist) ) |>addCircleMarkers(data = trees_sample,radius =2,color ="#4CAF50",stroke =FALSE,fillOpacity =0.5,popup =~paste0("<b>Species:</b> ", spc_common, "<br>","<b>Status:</b> ", status) ) |>addLegend(position ="bottomright",colors =c("#5C7EA8", "#4CAF50"),labels =c("Council Districts", "Tree Points"),opacity =0.8,title =paste0("NYC Tree Map (", tree_count, " Trees)") )
TipWhat Does This Show?
This Leaflet map shows a 50,000-tree sample from the NYC Street Tree Census, with green points representing trees and council district boundaries, outlined in blue, showing how coverage varies across the city. Clicking on a point reveals tree details, helping highlight species diversity and basic maintenance needs.
The map makes it easy to see which areas have more shade and tree coverage, and which areas may face hotter temperatures and lower air quality due to fewer trees. Even as a sample, it clearly shows differences in tree distribution and supports directing more planting and maintenance to neighborhoods that need it most.
This code matches each tree to the City Council District it belongs to and adds a borough label. Then, it saves the result as trees_joined.rds so I can use it later without re-running the join.
This table shows how many trees are located in each NYC Council District. The districts are sorted from the highest number of trees to the lowest so it is easy to see which areas have the most tree coverage. Council District 51 (Staten Island) has the most trees with 52,728trees. Districts with fewer trees may have reduced shade and higher heat exposure, showing where additional tree planting and maintenance could be most beneficial. This comparison helps guide decisions on how to allocate resources more effectively.
Highest Density Of Trees In Which Council District?
In this table, we are shown which NYC Council Districts has the most trees relative to their land area. Tree density helps us see which districts have more concentrated tree coverage, not just the highest count. The district with the highest tree density is District 9 in Manhattan with a density of 0.00015 trees per square unit. Districts with higher tree density tend to have stronger shade and environmental benefits, while lower-density districts may need more planting and tree care. This comparison helps identify which districts could benefit most from additional tree planting and long-term canopy investment.
Highest Fraction Of Dead Trees in Which Council District?
This analysis shows where tree health may need more attention. The highest share of dead trees is in District16 in Bronx, with 0.057 of its trees recorded as dead. This might be caused by stress, disease, or limited maintenance. Districts with a higher share of dead trees may face greater safety risks and reduced shade, showing a need for more focused tree care and replanting. This helps the city decide where to focus tree care and new plantings so all neighborhoods can benefit from healthy trees.
Most Common Tree Species In Manhatthan
Show The Code
library(DT)# table for manhatthanmanhattan_species <- trees_joined |>st_drop_geometry() |>filter(borough =="Manhattan") |>filter(!is.na(spc_common), spc_common !="") |>mutate(Species = tools::toTitleCase(spc_common)) |>count(Species, sort =TRUE) |>rename(Count = n)manhattan_species$Count <-format(manhattan_species$Count, big.mark =",")datatable( manhattan_species,rownames =TRUE,caption ="Most Common Tree Species in Manhattan",options =list(pageLength =10, scrollX =TRUE)) |>formatStyle("Species",target ="row",backgroundColor =styleEqual( manhattan_species$Species[1],"lightgreen" ) )
TipAnswer
This analysis looks at which tree species appear most often in Manhattan. The most common tree species in Manhattan is Honeylocust, with 13,644 recorded trees. This helps the city decide which trees to plant and how to care for them. It also shows whether Manhattan has a mix of different tree species or mostly the same kinds. Knowing which species are most common helps guide tree care and future planting, especially to keep trees healthy and maintain species diversity.
Tree Species Closest To The Baruch Campus
Show The Code
library(sf)library(dplyr)library(leaflet)# create a WGS84 point for baruch new_st_point <-function(lat, lon){st_sfc(st_point(c(lon, lat))) |>st_set_crs("WGS84")}baruch_point <-new_st_point(40.7402, -73.9834)# nearest tree to baruchnearest_tree <- trees_pts |>mutate(distance =as.numeric(st_distance(geometry, baruch_point))) |>arrange(distance) |>slice(1)# convert nearest tree for mappingnearest_tree_pt <- nearest_tree |>st_transform(4326)baruch_lat <-40.7402baruch_lon <--73.9834leaflet() |>addProviderTiles("CartoDB.Positron") |>addCircleMarkers(lng = baruch_lon, lat = baruch_lat,radius =10, color ="#8B4513", # baruch collegefillOpacity =0.9, stroke =FALSE,label ="Baruch College",popup ="Baruch College" ) |>addCircleMarkers(data = nearest_tree_pt,radius =10, color ="#228B22", # treefillOpacity =0.9, stroke =FALSE,label =~paste0("Nearest Tree: ", spc_common),popup =~paste0("<b>Nearest Tree</b><br>","Species: ", spc_common, "<br>","Status: ", status, "<br>","Distance: ", round(distance, 2), " meters" ) ) |>addLegend(position ="bottomright",title ="Map Legend",colors =c("#8B4513", "#228B22"),labels =c("Baruch College", "Nearest Tree"),opacity =0.9 ) |>setView(lng = baruch_lon, lat = baruch_lat, zoom =17)
TipAnswer
This map shows the street tree closest to Baruch College. The brown marker identifies the campus location, and the green marker highlights the nearest tree, including its species, condition, and distance. The nearest recorded tree is a Callery pear with a status of Alive, located about 33.75 meters from campus. This shows how nearby street trees can provide shade, comfort, and better air quality for students and pedestrians, and how tree data can help evaluate conditions around important public areas.